/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Operations.Geometry;

import Operations.*;
import java.awt.image.BufferedImage;

/**
 *
 * @author littlechild
 */
public class OpPerspective implements Operation{
    double phi;
    double theta;
    double f;
    double Xg,Yg,Zg;
    double cosphi;
    double sinphi;
    double costheta;
    double sintheta;
    public BufferedImage operate(BufferedImage image, double[] paras) {
        Zg=paras[0];
        phi=paras[1]/180.0*Math.PI;
        theta=paras[2]/180.0*Math.PI;
        f=paras[3];
        setXgYg(image);
        setComputeThetaPhi();
        Dimension cornerPoints[]=new Dimension[]{
            getNewPoint(0,0),
            getNewPoint(image.getWidth(),0),
            getNewPoint(0,image.getHeight()),
            getNewPoint(image.getWidth(),image.getHeight())
        };
         Rectangle region=generateRegion(cornerPoints);
        BufferedImage returnImage=new BufferedImage((int)(region.getWidth()+0.99),(int)(region.getHeight()+0.99),image.getType());
        for(int i=0;i!=returnImage.getWidth();++i) {
            for(int j=0;j!=returnImage.getHeight();++j) {
                Dimension d=getOriginalPoint(i+region.getX(),j+region.getY());
                double x0=d.x;
                double y0=d.y;
                // may be on the boundary
                if(x0>-1 && y0>-1 && x0<=image.getWidth() && y0<=image.getHeight())
                    returnImage.setRGB(i, j, Utilities.doubleLinearInterpolate(image, x0, y0));
            }
        }
        return returnImage;
    }
    void setXgYg(BufferedImage image) {
        //Xg=Yg=0;
        Xg=1.0/2*image.getWidth();
        Yg=2.0/2*image.getHeight();
    }
    private Dimension getNewPoint(double x,double y ) {
        double denominator;
        denominator=-(x-Xg)*sintheta*sinphi
                -(y-Yg)*costheta*sinphi
                +Zg*cosphi+f;
        Dimension p=new Dimension();
        p.x=f*((x-Xg)*costheta-(y-Yg)*sintheta)/denominator;
        p.y=f*((x-Xg)*sintheta*cosphi
                +(y-Yg)*costheta*cosphi+Zg*sinphi)
                /denominator;
        return p;
    }
    private Dimension getOriginalPoint(double xa,double ya) {
        double x=costheta*(-costheta*sinphi*Xg*ya
                +Zg*costheta*sintheta*sinphi*f
                -f*sintheta*costheta*ya
                -cosphi*ya*Zg*sintheta*costheta
                +Zg*sintheta*sintheta*xa-cosphi*f*costheta*Xg
                +cosphi*f*sintheta*sintheta*xa
                -cosphi*xa*f-xa*Zg)/(cosphi*f+ya*sinphi)/(sintheta*sintheta-1);
        double y=-(sintheta*xa*Zg+cosphi*f*xa*sintheta
                -ya*sinphi*Yg+costheta*f*Zg*sinphi
                -cosphi*costheta*ya*Zg-costheta*ya*f
                -f*cosphi*Yg)/(cosphi*f+ya*sinphi);
        return new Dimension(x,y);
    }
    private Rectangle generateRegion(Dimension[] cornerPoints) {
        double leftmost=Double.MAX_VALUE;
        double rightmost=Double.MIN_VALUE;
        double upmost=Double.MAX_VALUE;
        double downmost=Double.MIN_VALUE;
        for(int i=0;i!=cornerPoints.length;++i) {
            if(cornerPoints[i].getWidth()<leftmost)
                leftmost=cornerPoints[i].getWidth();
            if(cornerPoints[i].getWidth()>rightmost)
                rightmost=cornerPoints[i].getWidth();
            if(cornerPoints[i].getHeight()<upmost)
                upmost=cornerPoints[i].getHeight();
            if(cornerPoints[i].getHeight()>downmost) 
                downmost=cornerPoints[i].getHeight();
        }
        return new Rectangle(leftmost,upmost,rightmost-leftmost,downmost-upmost);
    }

    private void setComputeThetaPhi() {
           cosphi=Math.cos(phi);
           sinphi=Math.sin(phi);
           costheta=Math.cos(theta);
           sintheta=Math.sin(theta);
    }
}